#!/usr/bin/env python3
# -*- coding:utf-8 -*-

import os
import sys
import cmd
import logging
from prettytable import PrettyTable

try:
    from ctools.logext import *
# except (ImportError, ModuleNotFoundError)
except Exception as e:
    raise e

_prompt = os.path.basename(__file__).split(".")[0] + '=> '

class NewLoop(Exception):
    pass

class MyCmd(cmd.Cmd):
    def __init__(self, prompt=_prompt,
                tryshell=False, autorep=True,
                exc_break=True,
                comment_prefix='#') -> None:
        super().__init__()
        self.prompt = prompt

        self._params = {
            # key: val
            # key: (val, help_message)
            'tryshell': tryshell,
            'autorep': autorep,
            'exc_break': exc_break,
            'comment_prefix': comment_prefix,
        }

        self._param_helps = {
            'tryshell': 'try shell when error',
            'autorep': "auto repeat when Enter",
        }

    def __del__(self):
        # super() no __del__
        # super().__del__()
        pass

    @staticmethod
    def is_yes(val: str|bool|int):
        if isinstance(val, (int, bool)):
           return bool(val)

        try:
            ons = ('1', 'on', 'true', 'yes', 'y', 'enable')
            if val.lower() in ons:
                return True
        except:
            pass

        return False

    def param_value(self, key: str, default=None):
        val = self._params.get(key, default)
        return val

    def param_help(self, key: str, default=''):
        val = self._param_helps.get(key, default)
        return val

    def param_yes(self, key: str):
        return self.is_yes(self.param_value(key))

    def _try_shell(self):
        try:
            return self.param_yes('tryshell')
        except:
            return False

    def _new_loop(self):
        raise NewLoop()

    def cmdloop(self):
        while True:
            try:
                super().cmdloop()
                break
            except (KeyboardInterrupt, NewLoop):
                self.lastcmd = ''
                print()
            except Exception as e:
                if self.param_yes('exc_break'):
                    raise e
                self._new_loop()

    def emptyline(self):
        if self.param_yes('autorep'):
            return super().emptyline()

    def default(self, line):
        if self._try_shell():
            return self.do_shell(line)

        prefix = self.param_value('comment_prefix', '')
        if prefix and line.startswith(prefix):
            return

        self.lastcmd = ''
        return super().default(line)

    def _quit(self, line):
        print("\nExiting...")
        return True

    def _clear(self, line):
        try:
            os.system('clear')  # Linux/Win Powershell
        except:
            os.system('cls')    # Win Cmd
        # can not input after clean, use new loop to fix
        self._new_loop()

    def _complete_params(self, text, *ignored):
        return [a for a in self._params.keys() if a.startswith(text)]

    def _print_params(self, line):
        field_names = ('command', 'value', 'help',)
        table = PrettyTable(field_names=field_names, align='l')

        line = line.strip()
        if line:
            max_len = 9999999999
            keys = (line,)

            line = line.replace('\t', ' ')
            if line.count(' ') >= 0:
                keys = line.split()
        else:
            max_len = 60
            keys = list(self._params.keys())
            for k in self._param_helps.keys():
                if k not in keys:
                    keys.append(k)

        for k in keys:
            key = k
            val = self.param_value(key, '')
            helpmsg = self.param_help(key)

            if len(str(val)) > max_len:
                val = f"{str(val)[:max_len-3]}..."

            table.add_row((key, val, helpmsg))

        table.max_width = 120
        print(table.get_string())

    def do_shell(self, line):
        '''
        execute shell command
        '''
        ret = os.system(line)
        if ret:
            self.lastcmd = ''

    def do_quit(self, line):
        '''
        quit cli
        '''
        return self._quit(line)

    def do_q(self, line):
        '''
        alias to quit
        '''
        return self._quit(line)

    def do_exit(self, line):
        '''
        alias to quit
        '''
        return self._quit(line)

    def do_EOF(self, line):
        '''
        quit cli
        alias to Ctrl+D(Linux) Ctrl+Z(Win)
        '''
        return self._quit(line)

    def do_clear(self, line):
        '''
        clear screen
        '''
        return self._clear(line)

    def do_cls(self, line):
        '''
        clear screen
        '''
        return self._clear(line)

    def do_reset(self, line):
        '''
        start a new loop
        '''
        return self._new_loop()

    def complete_print(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def complete_pri(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def complete_pr(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def complete_p(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def do_print(self, line):
        '''
        show variables
        '''
        return self._print_params(line)

    def do_pri(self, line):
        '''
        alias to print
        '''
        return self._print_params(line)

    def do_pr(self, line):
        '''
        alias to print
        '''
        return self._print_params(line)

    def do_p(self, line):
        '''
        alias to print
        '''
        return self._print_params(line)

    def complete_set(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def complete_unset(self, text, *ignored):
        return self._complete_params(text, *ignored)

    def do_set(self, line: str):
        '''
        set <var> [val]
        '''
        line = line.strip().rstrip(';').rstrip()
        line = line.expandtabs(tabsize=1)
        if line.count(' ') == 0:
            print(self.do_set.__doc__)
            return

        key, value = line, ''
        if line.count(' ') > 0:
            key, value = line.split(' ', 1)

        self._params[key] = value

        print(f"set {key} {value}")

    def do_unset(self, line: str):
        '''
        unset <var> [val]
        '''
        line = line.strip()
        line = line.replace('\t', ' ')
        if line.count(' ') == 0:
            print(self.do_unset.__doc__)
            return

        keys = (line,)
        if line.count(' ') >= 0:
            keys = line.split()

        for k in keys:
            if k in self._params:
                del self._params[k]

    def do_del(self, line: str):
        '''
        alias to unset
        del <var> [val]
        '''
        return self.do_unset(line)

    def do_cwd(self, line:str):
        '''
        print name of current/working directory
        diff with shell 'pwd' command
        '''

        print(os.getcwd())

    def do_chdir(self, line: str):
        '''
        change directory
        diff with shell 'cd' command
        '''
        try:
            os.chdir(line)
            print(os.getcwd())
        except FileNotFoundError as e:
            print(f'No such directory: {line}')
        except Exception as e:
            raise e

    def do_eval(self, line:str):
        '''
        eval <cmd>
        '''
        try:
            return eval(line)
        except Exception as e:
            print(e)

    def do_export(self, line: str):
        # TODO
        pass

    def do_unexport(self, line: str):
        # TODO
        pass

def main():
    import argparse

    parser = argparse.ArgumentParser()
    parser.description = '%(prog)s: interactive cli'

    parser.add_argument("-p", "--prompt", type=str, default=_prompt, help="prompt")
    parser.add_argument("-s", "--shell", action='store_true', default=True, help="tryshell")
    parser.add_argument("command", type=str, nargs='*',
                        help="command and args, use '{} help' for more details".format(
                                os.path.basename(sys.argv[0])))

    args = parser.parse_args()

    cli = MyCmd(prompt=args.prompt, tryshell=args.shell)
    if args.command:
        return cli.onecmd(' '.join(args.command))

    cli.cmdloop()

if __name__ == "__main__":
    main()
